package oneD.fem.algorithm;

import inf.jlinalg.Array2DMatrix;
import inf.jlinalg.ArrayVector;
import inf.jlinalg.BLAM;
import inf.jlinalg.IMatrix;
import inf.jlinalg.IVector;

public class Tensor2 implements ITensor {

	IMatrix A;

	/**
	 * initialize an identity tensor with dimension n
	 * 
	 * @param n
	 */
	public Tensor2(int n) {
		setIdentity(n);
	}

	/**
	 * 
	 * create tensor from matrix
	 * 
	 * @param sigma
	 */
	public Tensor2(IMatrix sigma) {
		A = sigma;
	}

	/**
	 * 
	 * create tensor from vector
	 * 
	 * @param sigma
	 */
	public Tensor2(IVector sigma) {
		if (sigma.getSize() == 3) {
			A = new Array2DMatrix(2, 2);
			double sigmaxx = sigma.get(0);
			double sigmayy = sigma.get(1);
			double sigmaxy = sigma.get(2);
			A.set(0, 0, sigmaxx);
			A.set(0, 1, sigmaxy);
			A.set(1, 0, sigmaxy);
			A.set(1, 1, sigmayy);
		}
		if (sigma.getSize() == 6) {
			A = new Array2DMatrix(3, 3);
			double sigmaxx = sigma.get(0);
			double sigmayy = sigma.get(1);
			double sigmazz = sigma.get(2);
			double sigmaxy = sigma.get(3);
			double sigmayz = sigma.get(4);
			double sigmazx = sigma.get(5);
			A.set(0, 0, sigmaxx);
			A.set(0, 1, sigmaxy);
			A.set(0, 2, sigmazx);
			A.set(1, 0, sigmaxy);
			A.set(1, 1, sigmayy);
			A.set(1, 2, sigmayz);
			A.set(2, 0, sigmazx);
			A.set(2, 1, sigmayz);
			A.set(2, 2, sigmazz);
		}
	}

	public IMatrix getA() {
		return A;
	}

	/**
	 * 
	 * @return vector form of this tensor
	 */
	public IVector getVectorForm() {
		IVector v = new ArrayVector(dimension() * (dimension() + 1) / 2);
		if (dimension() == 2) {
			v.set(0, A.get(0, 0));
			v.set(1, A.get(1, 1));
			v.set(2, A.get(0, 1));
		} else if (dimension() == 3) {
			v.set(0, A.get(0, 0));
			v.set(1, A.get(1, 1));
			v.set(2, A.get(2, 2));
			v.set(3, A.get(0, 1));
			v.set(4, A.get(1, 2));
			v.set(5, A.get(0, 2));
		} else
			return null;
		return v;
	}

	@Override
	public double get(int... I) {
		return A.get(I[0], I[1]);
	}

	@Override
	public double trace() {
		double tmp = 0;
		for (int i = 0; i < A.getRowCount(); i++)
			tmp += A.get(i, i);
		return tmp;
	}

	@Override
	public int rank() {
		return 2;
	}

	@Override
	public Object clone() {
		Tensor2 theClone = new Tensor2((IMatrix) this.A.clone());
		return theClone;
	}

	@Override
	public int dimension() {
		return A.getRowCount();
	}

	private boolean checkCompatible(ITensor B, int rank) {
		if (B.rank() != rank) {
			System.out.println("tensor rank is not compatible");
			return false;
		}
		if (B.dimension() != this.dimension()) {
			System.out.println("tensor dimension is not compatible");
			return false;
		}
		return true;
	}

	@Override
	public ITensor contraction(ITensor B) {
		if (!checkCompatible(B, 1))
			return null;
		else {
			IVector v = new ArrayVector(B.dimension());
			for (int i = 0; i < v.getSize(); i++) {
				double tmp = 0;
				for (int j = 0; j < A.getColumnCount(); j++)
					tmp += A.get(i, j) * B.get(j);
				v.set(i, tmp);
			}
			return new Tensor1(v);
		}
	}

	@Override
	public Tensor2 multiply(ITensor B) {
		if (!checkCompatible(B, this.rank()))
			return null;
		else {
			IMatrix C = (IMatrix) this.A.clone();
			for (int i = 0; i < C.getRowCount(); i++)
				for (int j = 0; j < C.getColumnCount(); j++)
					C.set(i, j, C.get(i, j) * B.get(i, j));
			return new Tensor2(C);
		}
	}

	@Override
	public double dot(ITensor B) {
		if (!checkCompatible(B, this.rank()))
			return 0;
		else {
			double tmp = 0;
			for (int i = 0; i < A.getRowCount(); i++)
				for (int j = 0; j < A.getColumnCount(); j++)
					tmp += A.get(i, j) * B.get(i, j);
			return tmp;
		}
	}

	@Override
	public Tensor4 product(ITensor B) {
		if (!checkCompatible(B, this.rank()))
			return null;
		else {
			double[][][][] C = new double[dimension()][][][];
			for (int i = 0; i < dimension(); i++) {
				C[i] = new double[dimension()][][];
				for (int j = 0; j < dimension(); j++) {
					C[i][j] = new double[dimension()][];
					for (int k = 0; k < dimension(); k++)
						C[i][j][k] = new double[dimension()];
				}
			}
			for (int i = 0; i < dimension(); i++)
				for (int j = 0; j < dimension(); j++)
					for (int k = 0; k < dimension(); k++)
						for (int l = 0; l < dimension(); l++)
							C[i][j][k][l] = A.get(i, j) * B.get(k, l);
			return new Tensor4(C);
		}
	}

	@Override
	public Tensor2 add(double alpha, ITensor x) {
		return add(alpha, (Tensor2) x);
	}

	public Tensor2 add(double alpha, Tensor2 x) {
		IMatrix B = (IMatrix) getA().clone();
		BLAM.add(alpha, x.getA(), B);
		return new Tensor2(B);
	}

	public Tensor2 getDeviatoricTensor() {
		IMatrix S = (IMatrix) A.clone();
		int n = dimension();
		double tr = trace();
		for (int i = 0; i < A.getRowCount(); i++)
			S.set(i, i, S.get(i, i) - tr / n);
		return new Tensor2(S);
	}

	private void setIdentity(int n) {
		A = new Array2DMatrix(n, n);
		for (int i = 0; i < n; i++)
			A.set(i, i, 1);
	}

}
